package eg.edu.guc.met.agent;

import java.util.PriorityQueue;
import java.util.Vector;

import org.rlcommunity.rlglue.codec.AgentInterface;
import org.rlcommunity.rlglue.codec.types.Action;
import org.rlcommunity.rlglue.codec.types.Observation;
import org.rlcommunity.rlglue.codec.util.AgentLoader;

import eg.edu.guc.met.engine.Cell;

public class WumpusAgent implements AgentInterface {

	final int EAGLE = 1; // For eagle-mode
	final int AGENT = 2; // For agent-mode
	static int agenda; // Keeps track of the mode
	final static String[] strategies = {"DFS","BFS","IDS","UCS","ASS"};
	// The different search strategies
	static int strategyIndex = -1;
	// An index into the strategies array
	static Vector<Node> q; // The search queue
	static Vector<Node> partialStateNodes;
	// Nodes to be updated by an observation
	static Vector<Action> pathToGoal;
	// Sequence of actions constructed by the eagle
	static int pathToGoalIndex;

	Vector<Node> allNodes;
	static int cutOff = 0;

	// An index into the pathToGoal array

	static boolean firstFlag = true;
	static boolean secondFlag = true;

	public static int goldX;
	public static int goldY;

	@Override
	public void agent_cleanup() {

	}

	@Override
	public void agent_end(double arg0) {

	}

	@Override
	public String agent_message(String arg0) {
		return null;
	}

	@Override
	public Action agent_start(Observation o) {
		allNodes = new Vector<Node>();
		strategyIndex = strategyIndex + 1;
		partialStateNodes = constructNewStateNodes();
		q = new Vector<Node>();
		agenda = EAGLE;
		pathToGoalIndex = -1;
		Action a = new Action(0, 0, 1);
		a.setChar(0, '.');
		return a;
	}

	// initialize the new node at 0,0
	private Vector<Node> constructNewStateNodes() {
		Cell initialCell = new Cell();
		Node node = new Node(null, initialCell, 1, true, false, 0, 0, 0, 0);
		node.actionFromParent = '.';
		Vector<Node> list = new Vector<Node>();
		list.add(node);

		return list;
	}

	@Override
	public Action agent_step(double arg0, Observation o) {

		if (strategies[strategyIndex].equals("ASS") && firstFlag) {
			if (!secondFlag) {
				getGoldPlace(o);
				Action getOut = new Action();
				getOut.charArray = new char[] { '.' };
				firstFlag = false;
				secondFlag = false;
				return getOut;
			} else {
				return exploreMap();
			}
		} else {
			if (agenda == EAGLE) {
				// update the Partial state nodes with the set of triples of
				// observations
				Vector<Node> successorStates = updateWorkingNodeSet(
						partialStateNodes, o);
				enqueue(successorStates);
				if (q.isEmpty()) {
					Action a = new Action(0, 0, 1);
					a.setChar(0, 'x');
					return a;
				}
				Node first = q.remove(0);
				if (isGoal(first)) {
					pathToGoal = createPathToGoal(first);
					agenda = AGENT;
					Action a = new Action(0, 0, 1);
					a.setChar(0, '.');
					return a;
				}
				// do not explore duplicates
				partialStateNodes = getSuccessorStates(first);
				return getCellsNeededForDiscovery(partialStateNodes);
			}
			if (agenda == AGENT) {
				pathToGoalIndex = pathToGoalIndex + 1;
				// 3ashan ba add them bel sha2loob (last action .. el ablo .. el
				// ablo ....)
				int index = pathToGoal.size() - pathToGoalIndex - 1;
				if (index > -1) {
					return pathToGoal.get(index);
				} else {
					Action climb = new Action();
					climb.charArray = new char[] { 'c' };
					return climb;
				}
			}
			return null;
		}
	}

	private Action exploreMap() {
		Action action = new Action();
		action.charArray = new char[] { 'q' };
		action.intArray = new int[(2 * 12 * 12) + 1];
		action.intArray[0] = 12 * 12;
		int array_counter = 1;
		for (int i = 0; i < 12; i++) {
			for (int j = 0; j < 12; j++) {
				action.intArray[array_counter++] = j;
				action.intArray[array_counter++] = i;
			}
		}
		secondFlag = false;
		return action;
	}

	private void getGoldPlace(Observation o) {
		int i = 0;
		for (; i < o.intArray.length; i += 3) {
			if (o.intArray[i] == 1) {
				break;
			}
		}
		i = i / 3;
		goldX = i % 12;
		goldY = i / 12;

	}

	/**
	 * this method return the action in form of two arrays I and C C contains
	 * only 'q' I contains how many cells will be discovered in the next step
	 * which is here the size of the Node vector then the x and y of each node
	 * 
	 * @author Mina Zaki
	 * @param partialStateNodes
	 * @return
	 */
	private Action getCellsNeededForDiscovery(Vector<Node> partialStateNodes) {
		Action action = new Action();
		char[] C = new char[1];
		int[] I = new int[(partialStateNodes.size() * 2) + 1];

		action.charArray = C;
		action.intArray = I;

		C[0] = 'q';
		I[0] = partialStateNodes.size();

		int stateNodesIndex = 0;
		for (int i = 1; i < I.length; stateNodesIndex++) {
			I[i++] = partialStateNodes.get(stateNodesIndex).x;
			I[i++] = partialStateNodes.get(stateNodesIndex).y;
		}

		return action;
	}

	private Vector<Node> getSuccessorStates(Node parent) {
		Vector<Node> successorStates = new Vector<Node>();
		if (parent.cell.hasGold()) {
			Node successor = new Node(parent, parent.cell, parent.orientation,
					parent.arrow, true, parent.x, parent.y, parent.cost + 1,
					parent.depth + 1);
			successor.actionFromParent = 'g';
			if (!isDuplicate(successor))
				successorStates.add(successor);
		}
		if (parent.arrow) {
			Node successor = new Node(parent, parent.cell, parent.orientation,
					false, parent.holdingGold, parent.x, parent.y,
					parent.cost + 100, parent.depth + 1);
			successor.actionFromParent = 'a';
			if (!isDuplicate(successor))
				successorStates.add(successor);
		}
		Node successorForward = getForwardSuccesorState(parent);
		if (successorForward != null && !isDuplicate(successorForward))
			successorStates.add(successorForward);

		int orientationRight = parent.orientation + 1;
		if (orientationRight == 5)
			orientationRight = 1;
		Node successorRight = new Node(parent, parent.cell, orientationRight,
				parent.arrow, parent.holdingGold, parent.x, parent.y,
				parent.cost + 10, parent.depth + 1);
		successorRight.actionFromParent = 'r';
		if (!isDuplicate(successorRight))
			successorStates.add(successorRight);

		int orientationLeft = parent.orientation - 1;
		if (orientationLeft == 0)
			orientationLeft = 4;
		Node successorLeft = new Node(parent, parent.cell, orientationLeft,
				parent.arrow, parent.holdingGold, parent.x, parent.y,
				parent.cost + 10, parent.depth + 1);
		successorLeft.actionFromParent = 'l';
		if (!isDuplicate(successorLeft))
			successorStates.add(successorLeft);

		allNodes.addAll(successorStates);
		return successorStates;
	}

	Node getForwardSuccesorState(Node parent) {

		if (parent.orientation == 1 && parent.y <= 10) {
			Node successor = new Node(parent, new Cell(), parent.orientation,
					parent.arrow, parent.holdingGold, parent.x, parent.y + 1,
					parent.cost + 10, parent.depth + 1);
			successor.actionFromParent = 'f';
			return successor;
		} else if (parent.orientation == 2 && parent.x <= 10) {
			Node successor = new Node(parent, new Cell(), parent.orientation,
					parent.arrow, parent.holdingGold, parent.x + 1, parent.y,
					parent.cost + 10, parent.depth + 1);
			successor.actionFromParent = 'f';
			return successor;
		} else if (parent.orientation == 3 && parent.y >= 1) {
			Node successor = new Node(parent, new Cell(), parent.orientation,
					parent.arrow, parent.holdingGold, parent.x, parent.y - 1,
					parent.cost + 10, parent.depth + 1);
			successor.actionFromParent = 'f';
			return successor;
		} else if (parent.orientation == 4 && parent.x >= 1) {
			Node successor = new Node(parent, new Cell(), parent.orientation,
					parent.arrow, parent.holdingGold, parent.x - 1, parent.y,
					parent.cost + 10, parent.depth + 1);
			successor.actionFromParent = 'f';
			return successor;
		} else
			return null;

	}

	private void enqueue(Vector<Node> successorStates) {
		if (strategies[strategyIndex].equals("DFS")) {
			enqueueDFS(successorStates);
		}
		if (strategies[strategyIndex].equals("BFS")) {
			enqueueBFS(successorStates);
		}
		if (strategies[strategyIndex].equals("IDS")) {
			enqueueIDS(successorStates);
		}
		if (strategies[strategyIndex].equals("UCS")) {
			enqueueUCS(successorStates);
		}
		if (strategies[strategyIndex].equals("ASS")) {
			enqueueASS(successorStates);
		}
	}

	private void enqueueASS(Vector<Node> successorStates) {

		PriorityQueue<Node> PQ = new PriorityQueue<Node>();
		while (!successorStates.isEmpty())
			PQ.add(successorStates.remove(0));
		while (!q.isEmpty())
			PQ.add(q.remove(0));
		q.addAll(PQ);
	}

	private void enqueueUCS(Vector<Node> successorStates) {
		PriorityQueue<Node> PQ = new PriorityQueue<Node>();
		while (!successorStates.isEmpty())
			PQ.add(successorStates.remove(0));
		while (!q.isEmpty())
			PQ.add(q.remove(0));
		q.addAll(PQ);
	}

	private void enqueueIDS(Vector<Node> successorStates) {

		if (successorStates.isEmpty() && successorStates.get(0).depth <= cutOff) {

			q.addAll(0, successorStates);

		} else {
			// Reset with new cut-off
			if (q.isEmpty()) {

				cutOff++;
				allNodes.removeAllElements();
				partialStateNodes = constructNewStateNodes();

			}
		}

	}

	private void enqueueBFS(Vector<Node> successorStates) {

		q.addAll(successorStates);

	}

	private void enqueueDFS(Vector<Node> successorStates) {

		q.addAll(0, successorStates);

	}

	/**
	 * Create a path to goal using the attribute actionFromParent and Parent,
	 * starting from the goal Node and back to the start where the
	 * actionFromParent = '.'
	 * 
	 * @param last
	 * @return
	 */
	private Vector<Action> createPathToGoal(Node last) {

		Node current = last;
		Vector<Action> actionList = new Vector<Action>();

		while (current.actionFromParent != '.') {

			Action a = new Action(0, 0, 1);
			a.setChar(0, current.actionFromParent);
			actionList.add(a);

			current = current.parent;

		}

		return actionList;
	}

	private boolean isGoal(Node first) {
		if (first.holdingGold && first.x == 0 & first.y == 0)
			return true;

		return false;
	}

	/**
	 * Return TRUE if they are the same
	 * 
	 * @param n1
	 * @param n2
	 * @return
	 */
	private boolean compareNodes(Node n1, Node n2) {

		boolean samePosition = (n1.x == n2.x) && (n1.y == n2.y);
		boolean sameOrientation = (n1.orientation == n2.orientation)
				&& (n1.orientation == n2.orientation);
		boolean sameArrow = (n1.arrow == n2.arrow) && (n1.arrow == n2.arrow);
		boolean sameGold = (n1.holdingGold == n2.holdingGold)
				&& (n1.holdingGold == n2.holdingGold);

		return samePosition && sameOrientation && sameArrow && sameGold;

	}

	/**
	 * Compares a node to all previous nodes
	 * 
	 * @param n
	 * @return
	 */
	private boolean isDuplicate(Node n) {

		for (int i = 0; i < allNodes.size(); i++) {
			if (compareNodes(n, allNodes.get(i)))
				return true;
		}

		return false;
	}

	/**
	 * this method to update the partial state nodes with the observation in the
	 * loop i take on Node and update each property from the observation if
	 * observation 1 then it is true false otherwise observationIndex is the
	 * index looping on the observation since observation comes as an interger
	 * list in multiples of 3 and every three cosecutive ints is for an
	 * observation for a node in the format of (G,P,W)
	 * 
	 * @author Mina Zaki
	 * @param partialStateNodes2
	 * @param o
	 * @return
	 */
	private Vector<Node> updateWorkingNodeSet(Vector<Node> partialStateNodes2,
			Observation o) {
		int observationIndex = 0;
		for (int i = 0; i < partialStateNodes2.size(); i++) {
			partialStateNodes2.get(i).cell
					.setHasGold(o.intArray[observationIndex++] == 1 ? true
							: false);
			partialStateNodes2.get(i).cell
					.setHasPit(o.intArray[observationIndex++] == 1 ? true
							: false);
			partialStateNodes2.get(i).cell
					.setHasWumpus(o.intArray[observationIndex++] == 1 ? true
							: false);
			if (partialStateNodes2.get(i).cell.hasPit()
					|| partialStateNodes2.get(i).cell.hasWumpus()) {
				partialStateNodes2.remove(i);
				i--;
			}
		}
		return partialStateNodes2;
	}

	@Override
	public void agent_init(String arg0) {

	}

	public static void main(String[] args) {
		AgentLoader theAgentLoader = new AgentLoader(new WumpusAgent());
		// Create an environmentloader that will start the environment when its
		// run method is called

		// Create threads so that the agent and environment can run
		// asynchronously
		Thread agentThread = new Thread(theAgentLoader);

		// Start the threads
		agentThread.start();

		// Run the main method of the Skeleton Experiment, using the arguments
		// were were passed
		// This will run the experiment in the main thread.
	}
}